################################################################################
# The MIT License (MIT)
#
# Copyright (c) 2014 Kevin Haroldsen
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
################################################################################

import os
import uuid
import shutil
import struct
import functools
from .util import BootMode, RegValueType, HiveNavigator, guid_bracket, load_hive
from .errors import ParseError
from .objects import ObjectType, ObjectAppType, ObjectInheritClass, alias2guid, create_object_type
from .elements import ElementClass, ElementFormat, create_element_type
from .bcdobject import BCDObject, knownguids


class BCD:

    def __init__(self, src, readonly=True):
        self._src = src
        self._hive = load_hive(src, readonly)
        self._nav = HiveNavigator(self._hive, self._hive.root())
        self._objects = None

        if not self._nav.exists("/Description"):
            raise ParseError('key "/Description" not found')
        if not self._nav.exists("/Objects"):
            raise ParseError('key "/Objects" not found')

        if not self._nav.exists(os.path.join("/Objects", alias2guid('emssettings'))):
            # FIXME: should check more
            raise ParseError('object "emssettings" not found')

        if not self._nav.exists(os.path.join("/Objects", alias2guid('dbgsettings'))):
            # FIXME: should check more
            raise ParseError('object "dbgsettings" not found')

        if not self._nav.exists(os.path.join("/Objects", alias2guid('badmemory'))):
            # FIXME: should check more
            raise ParseError('object "badmemory" not found')

        if not self._nav.exists(os.path.join("/Objects", alias2guid('hypervisorsettings'))):
            # FIXME: should check more
            raise ParseError('object "hypervisorsettings" not found')

        if not self._nav.exists(os.path.join("/Objects", alias2guid('bootloadersettings'))):
            # FIXME: should check more
            raise ParseError('object "bootloadersettings" not found')

        if not self._nav.exists(os.path.join("/Objects", alias2guid('resumeloadersettings'))):
            # FIXME: should check more
            raise ParseError('object "resumeloadersettings" not found')

    @property
    @functools.lru_cache(maxsize=None)
    def description(self):
        return self._nav.value('KeyName', path='/Description')

    @property
    def objects(self):
        if self._objects is None:
            self._generate_objects()
        return self._objects

    @property
    def osloaders(self):
        return [i for i in self.objects if i._type_info == (1, 2, 3) or i._type_info == (1, 3, 3)]

    @property
    def default(self):
        try:
            return self[self.bootmgr.default]
        except AttributeError:
            return None

    def guid_to_known(self, guid):
        guid = guid_bracket(guid)

        v = knownguids.get(guid)

        if v:
            return v[0]

        default = self.default

        if default and guid == default.guid:
            return '{default}'
        else:
            return guid

    def has_boot_manager(self):
        return self._nav.exists(alias2guid('bootmgr'))

    def get_boot_manager_info(self):
        nav2 = self._nav.get(os.path.join("/Objects", alias2guid('bootmgr'), 'Elements', create_element_type(ElementClass.Library, ElementFormat.Device, 0x1, '11000001')))
        ret = _device_value_to_uuids(nav2.value('Element'))
        return {
            "boot_disk_uuid": ret[0],
            "boot_part_uuid": ret[1],
        }

    def is_ems_enabled(self):
        nav2 = self._nav.get("/Objects", alias2guid('emssettings'), "Elements", create_element_type(ElementClass.Library, ElementFormat.Boolean, 0x20, '16000020'))
        ret = nav2.value("Element")
        if ret == b'\x00':
            return False
        elif ret == b'\x01':
            return True
        else:
            assert False

    def get_debugger_info(self):
        assert False

    def get_hypervisor_debugger_info(self):
        assert False

    def set_ems_enabled(self, value):
        nav2 = self._nav.get("/Objects", alias2guid('emssettings'), "Elements", create_element_type(ElementClass.Library, ElementFormat.Boolean, 0x20, '16000020'))
        nav2.set_value("Element", b'\x01' if value else b'\x00')

    def set_debugger(self, debugger_type, **kwargs):
        # 0x0: No debugger
        # 0x1: 1394 (FireWire) debugger
        # 0x2: USB 2.0/3.0 debugger
        # 0x3: USB 3.0 debugger (specific variant)
        # 0x4: Serial debugger (COM port) ← This is what 0x4 means
        # 0x5: Network debugger (KDNET)
        # 0x6: Hyper-V debugger
        assert False

    def set_hypervisor_debugger(self, value, debugger_type, **kwargs):
        # 0x0: Disabled - No hypervisor debugging
        # 0x1: Serial - Hypervisor debugging over serial port
        # 0x2: 1394/FireWire - Hypervisor debugging over FireWire
        # 0x3: USB - Hypervisor debugging over USB

        # Serial Port number values:
        # 0x1: COM1
        # 0x2: COM2
        # 0x3: COM3
        # 0x4: COM4

        # Common baud rates:
        # 9600: Slow, reliable
        # 19200: Medium
        # 38400: Fast
        # 115200: Very fast - typical for modern debugging

        # 1394 Channel numbers:
        # 0x1: Channel 1
        # 0x2: Channel 2
        # 0x3: Channel 3
        # ...
        # 0x1F: Channel 31 (max)

        # USB target name (instead of port/baud)
        # USB port configuration may also be needed

        assert False

    def add_boot_manager(self, boot_mode, boot_disk_uuid, boot_part_uuid):
        assert isinstance(boot_mode, BootMode)
        assert isinstance(boot_disk_uuid, uuid.UUID)
        assert isinstance(boot_part_uuid, uuid.UUID)

        # firmware boot manager
        if boot_mode == BootMode.EFI:
            self._nav.delete_key(os.path.join("/Objects", alias2guid('fwbootmgr')))
            nav2 = self._nav.create_key(os.path.join("/Objects", alias2guid('fwbootmgr')))
            if True:
                nav3 = nav2.create_key('Description')
                if True:
                    nav3.set_value('Type', RegValueType.REG_DWORD, create_object_type(ObjectType.Application, ObjectAppType.FirmwareMgr, 0x1, 0x10100001))

                nav3 = nav2.create_key('Elements')
                if True:
                    # Display order
                    nav4 = nav3.create_key(create_element_type(ElementClass.Application, ElementFormat.ObjectList, 0x1, '24000001'))
                    if True:
                        nav4.set_value('Element', RegValueType.REG_MULTI_SZ, [alias2guid("bootmgr")])

                    # Timeout, 0 seconds
                    nav4 = nav3.create_key(create_element_type(ElementClass.Application, ElementFormat.Integer, 0x4, '25000004'))
                    if True:
                        nav4.set_value('Element', RegValueType.REG_BINARY, struct.pack('<Q', 0))
        elif boot_mode == BootMode.BIOS:
            self._nav.delete_key(os.path.join("/Objects", alias2guid('fwbootmgr')))
        else:
            assert False

        # boot manager
        self._nav.delete_key(os.path.join("/Objects", alias2guid('bootmgr')))
        nav2 = self._nav.create_key(os.path.join("/Objects", alias2guid('bootmgr')))
        if True:
            nav3 = nav2.create_key('Description')
            if True:
                nav3.set_value('Type', RegValueType.REG_DWORD, create_object_type(ObjectType.Application, ObjectAppType.FirmwareMgr, 0x2, 0x10100002))

            nav3 = nav2.create_key('Elements')
            if True:
                # Application device
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.Device, 0x1, '11000001'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_BINARY, _uuids_to_device_value(boot_disk_uuid, boot_part_uuid))

                if boot_mode == BootMode.EFI:
                    # Application path
                    nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.String, 0x2, '12000002'))
                    if True:
                        nav4.set_value('Element', RegValueType.REG_SZ, r'\EFI\Microsoft\Boot\bootmgfw.efi')
                elif boot_mode == BootMode.BIOS:
                    pass            # FIXME: really
                else:
                    assert False

                # Description
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.String, 0x4, '12000004'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_SZ, r'Windows Boot Manager')

                # Preferred locale
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.String, 0x5, '12000005'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_SZ, r'en-US')

                # Inherit
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.ObjectList, 0x6, '14000006'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_MULTI_SZ, [alias2guid("globalsettings")])

                # Timeout, 0 seconds
                nav4 = nav3.create_key(create_element_type(ElementClass.Application, ElementFormat.Integer, 0x4, '25000004'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_BINARY, struct.pack('<Q', 0))

                # Tools display order
                nav4 = nav3.create_key(create_element_type(ElementClass.Application, ElementFormat.ObjectList, 0x10, '24000010'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_MULTI_SZ, [alias2guid("memdiag")])

        # tools boot entry
        self._nav.delete_key(os.path.join("/Objects", alias2guid('memdiag')))
        nav2 = self._nav.create_key(os.path.join("/Objects", alias2guid('memdiag')))
        if True:
            nav3 = nav2.create_key('Description')
            if True:
                nav3.set_value('Type', RegValueType.REG_DWORD, create_object_type(ObjectType.Application, ObjectAppType.WinBootMgr, 0x5, 0x10200005))

            nav3 = nav2.create_key('Elements')
            if True:
                # Application device
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.Device, 0x1, '11000001'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_BINARY, _uuids_to_device_value(boot_disk_uuid, boot_part_uuid))

                # Application path
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.String, 0x2, '12000002'))
                if True:
                    if boot_mode == BootMode.EFI:
                        nav4.set_value('Element', RegValueType.REG_SZ, r'\EFI\Microsoft\Boot\memtest.efi')
                    elif boot_mode == BootMode.BIOS:
                        nav4.set_value('Element', RegValueType.REG_SZ, r'\boot\memtest.exe')
                    else:
                        assert False

                # Description
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.String, 0x4, '12000004'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_SZ, r'Windows Memory Diagnostic')

                # Preferred locale
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.String, 0x5, '12000005'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_SZ, r'en-US')

                # Inherit
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.ObjectList, 0x6, '14000006'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_MULTI_SZ, [alias2guid("globalsettings")])

                # Allow bad memory access
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.Boolean, 0xb, '1600000b'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_BINARY, b'\x01')

        # save
        self._hive.commit(self._src)

    def add_system(self, description, boot_disk_uuid, boot_part_uuid):
        assert isinstance(boot_disk_uuid, uuid.UUID)
        assert isinstance(boot_part_uuid, uuid.UUID)

        if self._nav.exists(os.path.join("/Objects", alias2guid('bootmgr'), create_element_type(ElementClass.Library, ElementFormat.String, 0x2, '12000002'))):
            boot_mode = BootMode.EFI
        else:
            boot_mode = BootMode.BIOS

        loader_uuid = uuid.uuid4()
        assert not self._nav.exists(guid_bracket(str(loader_uuid)))

        resume_uuid = uuid.uuid4()
        assert not self._nav.exists(guid_bracket(str(resume_uuid)))

        # boot entry
        nav2 = self._nav.create_key(guid_bracket(str(loader_uuid)))
        if True:
            nav3 = nav2.create_key('Description')
            if True:
                nav3.set_value('Type', RegValueType.REG_DWORD, create_object_type(ObjectType.Application, ObjectAppType.WinBootMgr, 0x4, 0x10200004))

            nav3 = nav2.create_key('Elements')
            if True:
                # Application device
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.Device, 0x1, '11000001'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_BINARY, _uuids_to_device_value(boot_disk_uuid, boot_part_uuid))

                # Application path
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.String, 0x2, '12000002'))
                if True:
                    if boot_mode == BootMode.EFI:
                        nav4.set_value('Element', RegValueType.REG_SZ, r'\windows\system32\winload.efi')
                    elif boot_mode == BootMode.BIOS:
                        nav4.set_value('Element', RegValueType.REG_SZ, r'\Windows\system32\winload.exe')
                    else:
                        assert False

                # Description
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.String, 0x4, '12000004'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_SZ, description)

                # Preferred locale
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.String, 0x5, '12000005'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_SZ, r'en-US')

                # Inherit
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.ObjectList, 0x6, '14000006'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_MULTI_SZ, [alias2guid("bootloadersettings")])

                # Isolated execution context
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.Boolean, 0x60, '16000060'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_BINARY, b'\x01')

                # Allowed in memory settings, but 0x15000075 is ???
                # nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.IntegerList, 0x77, '17000077'))        FIXME: only needed in EFI?
                # if True:
                #     nav4.set_value('Element', RegValueType.REG_BINARY, struct.pack('<Q', 0x15000075))

                # OS device
                nav4 = nav3.create_key(create_element_type(ElementClass.Application, ElementFormat.Device, 0x1, '21000001'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_BINARY, _uuids_to_device_value(boot_disk_uuid, boot_part_uuid))

                # System root
                nav4 = nav3.create_key(create_element_type(ElementClass.Application, ElementFormat.String, 0x2, '22000002'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_SZ, r'\windows')

                # Associated resume object
                nav4 = nav3.create_key(create_element_type(ElementClass.Application, ElementFormat.Object, 0x3, '23000003'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_SZ, guid_bracket(str(resume_uuid)))

                # Nx policy, 0 is opt in
                nav4 = nav3.create_key(create_element_type(ElementClass.Application, ElementFormat.Integer, 0x20, '25000020'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_BINARY, struct.pack('<Q', 0))

                # Boot menu policy, 1 is standard
                nav4 = nav3.create_key(create_element_type(ElementClass.Application, ElementFormat.Integer, 0xc2, '250000c2'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_BINARY, struct.pack('<Q', 0x1))

        # boot entry
        # FIXME: Windows Recovery Environment

        # resume entry
        nav2 = self._nav.create_key(guid_bracket(str(resume_uuid)))
        if True:
            nav3 = nav2.create_key('Description')
            if True:
                nav3.set_value('Type', RegValueType.REG_DWORD, create_object_type(ObjectType.Application, ObjectAppType.WinBootMgr, 0x4, 0x10200004))

            nav3 = nav2.create_key('Elements')
            if True:
                # Application device
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.Device, 0x1, '11000001'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_BINARY, _uuids_to_device_value(boot_disk_uuid, boot_part_uuid))

                # Application path
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.String, 0x2, '12000002'))
                if True:
                    if boot_mode == BootMode.EFI:
                        nav4.set_value('Element', RegValueType.REG_SZ, r'\EFI\Microsoft\Boot\winresume.efi')     # FIXME: really?
                    elif boot_mode == BootMode.BIOS:
                        nav4.set_value('Element', RegValueType.REG_SZ, r'\Windows\system32\winresume.exe')
                    else:
                        assert False

                # Description
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.String, 0x4, '12000004'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_SZ, r'Windows Resume Application')

                # Preferred locale
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.String, 0x5, '12000005'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_SZ, r'en-US')

                # Inherit
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.ObjectList, 0x6, '14000006'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_MULTI_SZ, [alias2guid("resumeloadersettings")])

                # Isolated execution context
                nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.Boolean, 0x60, '16000060'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_BINARY, b'\x01')

                # Allowed in memory settings, but 0x15000075 is ???
                # nav4 = nav3.create_key(create_element_type(ElementClass.Library, ElementFormat.IntegerList, 0x77, '17000077'))
                # if True:
                #     nav4.set_value('Element', RegValueType.REG_BINARY, struct.pack('<Q', 0x15000075))

                # Hiberfile path
                nav4 = nav3.create_key(create_element_type(ElementClass.Application, ElementFormat.String, 0x2, '22000002'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_SZ, r'\hiberfil.sys')

                # Boot menu policy
                nav4 = nav3.create_key(create_element_type(ElementClass.Application, ElementFormat.Integer, 0x8, '25000008'))
                if True:
                    nav4.set_value('Element', RegValueType.REG_BINARY, struct.pack('<Q', 0x1))

        # boot manager
        nav2 = self._nav.get(os.path.join("/Objects", alias2guid('bootmgr')))
        if True:
            # Default object
            defaultObjectKey = create_element_type(ElementClass.Application, ElementFormat.Object, 0x3, '23000003')
            if not nav2.exists(defaultObjectKey):
                nav2.create_key(defaultObjectKey).set_value('Element', RegValueType.REG_SZ, guid_bracket(str(loader_uuid)))
        if True:
            # Display order
            displayOrderKey = create_element_type(ElementClass.Application, ElementFormat.ObjectList, 0x1, '24000001')
            if not nav2.exists(displayOrderKey):
                nav2.create_key(displayOrderKey).set_value('Element', RegValueType.REG_MULTI_SZ, [guid_bracket(str(loader_uuid))])
            else:
                nav3 = nav2.get(displayOrderKey)
                if not nav3.value_exists('Element'):
                    nav3.set_value('Element', RegValueType.REG_MULTI_SZ, [guid_bracket(str(loader_uuid))])
                else:
                    nav3.set_value('Element', RegValueType.REG_MULTI_SZ, nav3.value('Element') + [guid_bracket(str(loader_uuid))])

        # save
        self._hive.commit(self._src)

    def __getitem__(self, guid):
        guid = guid_bracket(guid)
        for o in self.objects:
            if o.guid == guid or knownguids.get(o.guid, [None])[0] == guid:
                return o
        else:
            raise KeyError('No guid ' + guid)

    def __iter__(self):
        return iter(self.objects)

    def __getattr__(self, guid):
        try:
            return self[guid_bracket(guid)]
        except KeyError:
            raise AttributeError('No guid ' + guid)

    def __contains__(self, guid):
        try:
            self[guid]
            return True
        except KeyError:
            return False

    def __hash__(self):
        return hash(frozenset((hash(o) for o in self.objects)))

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        pass

    def _generate_objects(self):
        if self._nav.exists('/Objects'):
            self._objects = tuple((BCDObject(self, guid) for guid in self._nav.subkeys('/Objects')))
        else:
            self._objects = tuple()


def new_bcd(fname):
    selfDir = os.path.dirname(os.path.realpath(__file__))
    shutil.copy(os.path.join(selfDir, "BCD.minimal"), fname)

    hive = load_hive(fname, readonly=False)
    nav = HiveNavigator(hive, hive.root())

    # create "Description"
    nav2 = nav.create_key('Description')
    if True:
        nav2.set_value('KeyName', RegValueType.REG_SZ, 'BCD00000000')
        nav2.set_value('System', RegValueType.REG_DWORD, 0x1)                 # FIXME
        nav2.set_value('TreatAsSystem', RegValueType.REG_DWORD, 0x1)          # FIXME

    # create "Objects"
    nav2 = nav.create_key('Objects')

    # template object
    nav3 = nav2.create_key(alias2guid('emssettings'))
    if True:
        nav4 = nav3.create_key('Description')
        if True:
            nav4.set_value('Type', RegValueType.REG_DWORD, create_object_type(ObjectType.Inheritable, ObjectInheritClass.Library, 0x0, 0x20100000))

        nav4 = nav3.create_key('Elements')
        if True:
            # EMS enabled
            nav5 = nav4.create_key(create_element_type(ElementClass.Library, ElementFormat.Boolean, 0x20, '16000020'))
            if True:
                # disable EMS
                nav5.set_value('Element', RegValueType.REG_BINARY, b'\x00')

    # template object
    nav3 = nav2.create_key(alias2guid('dbgsettings'))
    if True:
        nav4 = nav3.create_key('Description')
        if True:
            nav4.set_value('Type', RegValueType.REG_DWORD, create_object_type(ObjectType.Inheritable, ObjectInheritClass.Library, 0x0, 0x20100000))

        nav4 = nav3.create_key('Elements')
        if True:
            # Debugger type, 0x0 means No Debugger
            nav5 = nav4.create_key(create_element_type(ElementClass.Library, ElementFormat.Integer, 0x11, '15000011'))
            if True:
                nav5.set_value('Element', RegValueType.REG_BINARY, struct.pack('<Q', 0x0))

    # template object
    nav3 = nav2.create_key(alias2guid('badmemory'))
    if True:
        nav4 = nav3.create_key('Description')
        if True:
            nav4.set_value('Type', RegValueType.REG_DWORD, create_object_type(ObjectType.Inheritable, ObjectInheritClass.Library, 0x0, 0x20100000))
        nav4 = nav3.create_key('Elements')

    # template object
    nav3 = nav2.create_key(alias2guid('globalsettings'))
    if True:
        nav4 = nav3.create_key('Description')
        if True:
            nav4.set_value('Type', RegValueType.REG_DWORD, create_object_type(ObjectType.Inheritable, ObjectInheritClass.Library, 0x0, 0x20100000))

        nav4 = nav3.create_key('Elements')
        if True:
            # Inherit
            nav5 = nav4.create_key(create_element_type(ElementClass.Library, ElementFormat.ObjectList, 0x6, '14000006'))
            if True:
                nav5.set_value('Element', RegValueType.REG_MULTI_SZ, [alias2guid("dbgsettings"), alias2guid("emssettings"), alias2guid("badmemory")])

    # template object
    nav3 = nav2.create_key(alias2guid('hypervisorsettings'))
    if True:
        nav4 = nav3.create_key('Description')
        if True:
            nav4.set_value('Type', RegValueType.REG_DWORD, create_object_type(ObjectType.Inheritable, ObjectInheritClass.Application, 0x3, 0x20200003))

        nav4 = nav3.create_key('Elements')
        if True:
            # Hypervisor debugger type, 0x0 means Disabled
            nav5 = nav4.create_key(create_element_type(ElementClass.Application, ElementFormat.Integer, 0xf3, '250000f3'))
            if True:
                nav5.set_value('Element', RegValueType.REG_BINARY, struct.pack('<Q', 0x0))

    # template objects
    nav3 = nav2.create_key(alias2guid('bootloadersettings'))
    if True:
        nav4 = nav3.create_key('Description')
        if True:
            nav4.set_value('Type', RegValueType.REG_DWORD, create_object_type(ObjectType.Inheritable, ObjectInheritClass.Application, 0x3, 0x20200003))

        nav4 = nav3.create_key('Elements')
        if True:
            # Inherit
            nav5 = nav4.create_key(create_element_type(ElementClass.Library, ElementFormat.ObjectList, 0x6, '14000006'))
            if True:
                nav5.set_value('Element', RegValueType.REG_MULTI_SZ, [alias2guid("globalsettings"), alias2guid("hypervisorsettings")])

    # template objects
    nav3 = nav2.create_key(alias2guid('resumeloadersettings'))
    if True:
        nav4 = nav3.create_key('Description')
        if True:
            nav4.set_value('Type', RegValueType.REG_DWORD, create_object_type(ObjectType.Inheritable, ObjectInheritClass.Application, 0x4, 0x20200004))

        nav4 = nav3.create_key('Elements')
        if True:
            # Inherit
            nav5 = nav4.create_key(create_element_type(ElementClass.Library, ElementFormat.ObjectList, 0x6, '14000006'))
            if True:
                nav5.set_value('Element', RegValueType.REG_MULTI_SZ, [alias2guid("globalsettings")])

    # save
    hive.commit(fname)


def _uuids_to_device_value(disk_uuid, part_uuid):
    result = bytearray(0x58)

    # type is qualified partition
    result[0x10] = 0x06

    # unknown
    result[0x18] = 0x48

    # partition id
    result[0x20:0x20 + 16] = part_uuid.bytes_le
    result[0x38:0x38 + 16] = disk_uuid.bytes_le

    return bytes(result)


def _device_value_to_uuids(device_value):
    assert len(device_value) == 0x58
    assert device_value[0x10] == 0x06
    assert device_value[0x18] == 0x48

    part_uuid_bytes = device_value[0x20:0x30]
    part_uuid = uuid.UUID(bytes_le=bytes(part_uuid_bytes))

    disk_uuid_bytes = device_value[0x38:0x48]
    disk_uuid = uuid.UUID(bytes_le=bytes(disk_uuid_bytes))

    return (disk_uuid, part_uuid)




def _uuids_to_device_value(disk_uuid, part_uuid):
    result = bytearray(0x58)

    # type is qualified partition
    result[0x10] = 0x06

    # unknown
    result[0x18] = 0x48

    # Partition UUID - handle fields separately
    part_bytes = part_uuid.bytes_le
    # First 4 bytes (time_low) in little-endian
    result[0x20:0x24] = part_bytes[0:4]
    # Next 2 bytes (time_mid) in little-endian  
    result[0x24:0x26] = part_bytes[4:6]
    # Next 2 bytes (time_hi_version) in little-endian
    result[0x26:0x28] = part_bytes[6:8]
    # The remaining 8 bytes (clock_seq + node)
    result[0x28:0x30] = part_bytes[8:16]
    
    # Disk UUID - same treatment
    disk_bytes = disk_uuid.bytes_le
    result[0x38:0x3C] = disk_bytes[0:4]
    result[0x3C:0x3E] = disk_bytes[4:6]
    result[0x3E:0x40] = disk_bytes[6:8]
    result[0x40:0x48] = disk_bytes[8:16]

    return bytes(result)


def _device_value_to_uuids(device_value):
    assert len(device_value) == 0x58
    assert device_value[0x10] == 0x06
    assert device_value[0x18] == 0x48

    # Reconstruct partition UUID from individual fields
    part_bytes = bytearray(16)
    part_bytes[0:4] = device_value[0x20:0x24]  # time_low
    part_bytes[4:6] = device_value[0x24:0x26]  # time_mid  
    part_bytes[6:8] = device_value[0x26:0x28]  # time_hi_version
    part_bytes[8:16] = device_value[0x28:0x30] # clock_seq + node
    
    part_uuid = uuid.UUID(bytes_le=bytes(part_bytes))

    # Reconstruct disk UUID from individual fields
    disk_bytes = bytearray(16)
    disk_bytes[0:4] = device_value[0x38:0x3C]  # time_low
    disk_bytes[4:6] = device_value[0x3C:0x3E]  # time_mid
    disk_bytes[6:8] = device_value[0x3E:0x40]  # time_hi_version
    disk_bytes[8:16] = device_value[0x40:0x48] # clock_seq + node
    
    disk_uuid = uuid.UUID(bytes_le=bytes(disk_bytes))

    return (disk_uuid, part_uuid)